//  Copyright (C) 2000 Constantin Kaplinsky. All Rights Reserved.
//
//  This file is part of the VNC system.
//
//  The VNC system is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; if not, write to the Free Software
//  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
//  USA.

// XCursor and RichCursor encodings
//
// Support for cursor shape updates for ClientConnection class.

#include "stdhdrs.h"
#include "vncviewer.h"
#include "ClientConnection.h"


void ClientConnection::ReadCursorShape(rfbFramebufferUpdateRectHeader *pfburh)
{
  vnclog.Print(6, "Receiving cursor shape update, cursor %dx%d\n",
               (int)pfburh->r.w, (int)pfburh->r.h);

  int bytesPerRow = (pfburh->r.w + 7) / 8;
  int bytesMaskData = bytesPerRow * pfburh->r.h;
  int bytesSourceData =
    pfburh->r.w * pfburh->r.h * (m_myFormat.bitsPerPixel / 8);
  CheckBufferSize(bytesMaskData);

  SoftCursorFree();

  if (pfburh->r.w * pfburh->r.h == 0)
    return;

  // Ignore cursor shape updates if requested by user
  if (m_opts.m_ignoreShapeUpdates) {
    int bytesToSkip = (pfburh->encoding == rfbEncodingXCursor) ?
      (6 + 2 * bytesMaskData) : (bytesSourceData + bytesMaskData);
    CheckBufferSize(bytesToSkip);
    ReadExact(m_netbuf, bytesToSkip);
    return;
  }

  // Read cursor pixel data
  rcSource = new COLORREF[pfburh->r.w * pfburh->r.h];

  if (pfburh->encoding == rfbEncodingXCursor) {
    CARD8 xcolors[6];
    ReadExact((char *)xcolors, 6);
    COLORREF rcolors[2];
    rcolors[1] = PALETTERGB(xcolors[0], xcolors[1], xcolors[2]);
    rcolors[0] = PALETTERGB(xcolors[3], xcolors[4], xcolors[5]);

    ReadExact(m_netbuf, bytesMaskData);
    int x, y, n, b;
    int i = 0;
    for (y = 0; y < pfburh->r.h; y++) {
      for (x = 0; x < pfburh->r.w / 8; x++) {
        b = m_netbuf[y * bytesPerRow + x];
        for (n = 7; n >= 0; n--)
          rcSource[i++] = rcolors[b >> n & 1];
      }
      for (n = 7; n >= 8 - pfburh->r.w % 8; n--)
        rcSource[i++] = rcolors[m_netbuf[y * bytesPerRow + x] >> n & 1];
    }
  } else {
    // rfb.EncodingRichCursor
    CheckBufferSize(bytesSourceData);
    ReadExact(m_netbuf, bytesSourceData);
    SETUP_COLOR_SHORTCUTS;
    char *p = m_netbuf;
    for (int i = 0; i < pfburh->r.w * pfburh->r.h; i++) {
      switch (m_myFormat.bitsPerPixel) {
        case 8:
          rcSource[i] = COLOR_FROM_PIXEL8_ADDRESS(p);
          p++;
          break;
        case 16:
          rcSource[i] = COLOR_FROM_PIXEL16_ADDRESS(p);
          p += 2;
          break;
        case 32:
          rcSource[i] = COLOR_FROM_PIXEL32_ADDRESS(p);
          p += 4;
          break;
      }
    }
  }

  // Read and decode mask data
  ReadExact(m_netbuf, bytesMaskData);

  rcMask = new bool[pfburh->r.w * pfburh->r.h];

  int x, y, n, b;
  int i = 0;
  for (y = 0; y < pfburh->r.h; y++) {
    for (x = 0; x < pfburh->r.w / 8; x++) {
      b = m_netbuf[y * bytesPerRow + x];
      for (n = 7; n >= 0; n--)
        rcMask[i++] = (b >> n & 1) != 0;
    }
    for (n = 7; n >= 8 - pfburh->r.w % 8; n--)
      rcMask[i++] = (m_netbuf[y * bytesPerRow + x] >> n & 1) != 0;
  }

  // Set remaining data associated with the cursor
  omni_mutex_lock l(m_cursorMutex);

  rcWidth = pfburh->r.w;
  rcHeight = pfburh->r.h;
  rcHotX = (pfburh->r.x < rcWidth) ? pfburh->r.x : rcWidth - 1;
  rcHotY = (pfburh->r.y < rcHeight) ? pfburh->r.y : rcHeight - 1;

  {
    omni_mutex_lock l(m_bitmapdcMutex);
    ObjectSelector b1(m_hBitmapDC, m_hBitmap);
    PaletteSelector ps1(m_hBitmapDC, m_hPalette);
    m_hSavedAreaDC = CreateCompatibleDC(m_hBitmapDC);
    m_hSavedAreaBitmap =
      CreateCompatibleBitmap(m_hBitmapDC, rcWidth, rcHeight);
  }

  SoftCursorSaveArea();
  SoftCursorDraw();

  rcCursorHidden = false;
  rcLockSet = false;

  prevCursorSet = true;
}


void ClientConnection::ReadCursorPos(rfbFramebufferUpdateRectHeader *pfburh)
{
  int x = (int)pfburh->r.x;
  if (x >= m_si.framebufferWidth)
    x = m_si.framebufferWidth - 1;
  int y = (int)pfburh->r.y;
  if (y >= m_si.framebufferHeight)
    y = m_si.framebufferHeight - 1;

  SoftCursorMove(x, y);
}


// This method should be used to prevent collisions between simultaneous
// framebuffer update operations and cursor drawing operations caused by
// movements of the pointing device.  The parameters denote a rectangle in
// which the mouse cursor should not be drawn.  Every subsequent call to this
// function expands the locked area, so previous locks remain active.

void ClientConnection::SoftCursorLockArea(int x, int y, int w, int h)
{
  omni_mutex_lock l(m_cursorMutex);

  if (!prevCursorSet)
    return;

  if (!rcLockSet) {
    rcLockX = x;
    rcLockY = y;
    rcLockWidth = w;
    rcLockHeight = h;
    rcLockSet = true;
  } else {
    int newX = (x < rcLockX) ? x : rcLockX;
    int newY = (y < rcLockY) ? y : rcLockY;
    rcLockWidth = (x + w > rcLockX + rcLockWidth) ?
      (x + w - newX) : (rcLockX + rcLockWidth - newX);
    rcLockHeight = (y + h > rcLockY + rcLockHeight) ?
      (y + h - newY) : (rcLockY + rcLockHeight - newY);
    rcLockX = newX;
    rcLockY = newY;
  }

  if (!rcCursorHidden && SoftCursorInLockedArea()) {
    SoftCursorRestoreArea();
    rcCursorHidden = true;
  }
}


// This function discards all locks performed since previous
// SoftCursorUnlockScreen() call.

void ClientConnection::SoftCursorUnlockScreen()
{
  omni_mutex_lock l(m_cursorMutex);

  if (!prevCursorSet)
    return;

  if (rcCursorHidden) {
    SoftCursorSaveArea();
    SoftCursorDraw();
    rcCursorHidden = false;
  }
  rcLockSet = false;
}


// Moves soft cursor to a particular location.  This function respects locking
// of screen areas, so when the cursor is moved in the locked area, it becomes
// invisible until SoftCursorUnlockScreen() is called.

void ClientConnection::SoftCursorMove(int x, int y)
{
  omni_mutex_lock l(m_cursorMutex);

  if (prevCursorSet && !rcCursorHidden) {
    SoftCursorRestoreArea();
    rcCursorHidden = true;
  }

  rcCursorX = x;
  rcCursorY = y;

  if (prevCursorSet && !(rcLockSet && SoftCursorInLockedArea())) {
    SoftCursorSaveArea();
    SoftCursorDraw();
    rcCursorHidden = false;
  }
}


// Free all data associated with cursor.

void ClientConnection::SoftCursorFree()
{
  omni_mutex_lock l(m_cursorMutex);

  if (prevCursorSet) {
    if (!rcCursorHidden)
      SoftCursorRestoreArea();
    DeleteObject(m_hSavedAreaBitmap);
    DeleteDC(m_hSavedAreaDC);
    delete[] rcSource;
    delete[] rcMask;
    prevCursorSet = false;
  }
}


//////////////////////////////////////////////////////////////////
//
// Low-level methods implementing software cursor functionality.
//

// Check if cursor is within locked part of screen.

bool ClientConnection::SoftCursorInLockedArea()
{
  return rcLockX < rcCursorX - rcHotX + rcWidth &&
         rcLockY < rcCursorY - rcHotY + rcHeight &&
         rcLockX + rcLockWidth > rcCursorX - rcHotX &&
         rcLockY + rcLockHeight > rcCursorY - rcHotY;
}


// Save screen data in memory buffer.

void ClientConnection::SoftCursorSaveArea()
{
  RECT r;
  SoftCursorToScreen(&r, NULL);
  int x = r.left;
  int y = r.top;
  int w = r.right - r.left;
  int h = r.bottom - r.top;

  omni_mutex_lock l(m_bitmapdcMutex);
  ObjectSelector b1(m_hBitmapDC, m_hBitmap);
  PaletteSelector ps1(m_hBitmapDC, m_hPalette);
  ObjectSelector b2(m_hSavedAreaDC, m_hSavedAreaBitmap);
  PaletteSelector ps2(m_hSavedAreaDC, m_hPalette);

  if (!BitBlt(m_hSavedAreaDC, 0, 0, w, h, m_hBitmapDC, x, y, SRCCOPY))
    vnclog.Print(0, "Error saving screen under cursor\n");
}


// Restore screen data saved in memory buffer.

void ClientConnection::SoftCursorRestoreArea()
{
  RECT r;
  SoftCursorToScreen(&r, NULL);
  int x = r.left;
  int y = r.top;
  int w = r.right - r.left;
  int h = r.bottom - r.top;

  omni_mutex_lock l(m_bitmapdcMutex);
  ObjectSelector b1(m_hBitmapDC, m_hBitmap);
  PaletteSelector ps1(m_hBitmapDC, m_hPalette);
  ObjectSelector b2(m_hSavedAreaDC, m_hSavedAreaBitmap);
  PaletteSelector ps2(m_hSavedAreaDC, m_hPalette);

  if (!BitBlt(m_hBitmapDC, x, y, w, h, m_hSavedAreaDC, 0, 0, SRCCOPY))
    vnclog.Print(0, "Error restoring screen under cursor\n");

  InvalidateScreenRect(&r);
}


void ClientConnection::SoftCursorDraw()
{
  int x, y, x0, y0;
  int offset;

  omni_mutex_lock l(m_bitmapdcMutex);
  ObjectSelector b(m_hBitmapDC, m_hBitmap);
  PaletteSelector p(m_hBitmapDC, m_hPalette);

  SETUP_COLOR_SHORTCUTS;

  for (y = 0; y < rcHeight; y++) {
    y0 = rcCursorY - rcHotY + y;
    if (y0 >= 0 && y0 < m_si.framebufferHeight) {
      for (x = 0; x < rcWidth; x++) {
        x0 = rcCursorX - rcHotX + x;
        if (x0 >= 0 && x0 < m_si.framebufferWidth) {
          offset = y * rcWidth + x;
          if (rcMask[offset])
            SETPIXEL(m_hBitmapDC, x0, y0, rcSource[offset]);
        }
      }
    }
  }

  RECT r;
  SoftCursorToScreen(&r, NULL);
  InvalidateScreenRect(&r);
}


// Calculate position, size and offset for the part of cursor located inside
// the framebuffer bounds.

void ClientConnection::SoftCursorToScreen(RECT *screenArea,
                                          POINT *cursorOffset)
{
  int cx = 0, cy = 0;

  int x = rcCursorX - rcHotX;
  int y = rcCursorY - rcHotY;
  int w = rcWidth;
  int h = rcHeight;

  if (x < 0) {
    cx = -x;
    w -= cx;
    x = 0;
  } else if (x + w > m_si.framebufferWidth) {
    w = m_si.framebufferWidth - x;
  }
  if (y < 0) {
    cy = -y;
    h -= cy;
    y = 0;
  } else if (y + h > m_si.framebufferHeight) {
    h = m_si.framebufferHeight - y;
  }

  if (w < 0) {
    cx = 0;  x = 0;  w = 0;
  }
  if (h < 0) {
    cy = 0;  y = 0;  h = 0;
  }

  if (screenArea != NULL)
    SetRect(screenArea, x, y, x + w, y + h);

  if (cursorOffset != NULL) {
    cursorOffset->x = cx;
    cursorOffset->y = cy;
  }
}
